CSHARP-5712: withTransaction API retries too frequently#1841
CSHARP-5712: withTransaction API retries too frequently#1841sanych-sun merged 15 commits intomongodb:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements the RetryabilityHelper.GetRetryDelayMs method to control retry delays with exponential backoff, addressing the issue of transactions retrying too frequently. The implementation adds jitter to prevent thundering herd problems and includes comprehensive parameter validation.
Key Changes
- Adds
GetRetryDelayMsmethod with exponential backoff calculation and random jitter - Removes unused
MongoDB.Driver.Core.Connectionsimport from test file - Adds comprehensive test coverage for valid inputs, edge cases, and parameter validation
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/MongoDB.Driver/Core/Operations/RetryabilityHelper.cs | Implements the core retry delay calculation with exponential backoff and jitter |
| tests/MongoDB.Driver.Tests/Core/Operations/RetryabilityHelperTests.cs | Adds unit tests for the new method and removes unused import |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Ensure.IsGreaterThan(backoffMax, backoffInitial, nameof(backoffMax)); | ||
|
|
||
| var j = __backoffRandom.NextDouble(); | ||
| return (int)(j * Math.Min(backoffMax, backoffInitial * Math.Pow(backoffBase, attempt - 1))); |
There was a problem hiding this comment.
The calculation backoffInitial * Math.Pow(backoffBase, attempt - 1) may overflow for large attempt values, which would then be capped by Math.Min. Consider adding overflow protection or documenting the maximum safe attempt value to prevent unexpected behavior.
| return (int)(j * Math.Min(backoffMax, backoffInitial * Math.Pow(backoffBase, attempt - 1))); | |
| // compute the largest exponent such that backoffInitial * backoffBase^exponent <= backoffMax | |
| var maxExponent = Math.Log(backoffMax / (double)backoffInitial, backoffBase); | |
| var effectiveExponent = attempt - 1; | |
| double delayWithoutJitter; | |
| if (effectiveExponent >= maxExponent) | |
| { | |
| delayWithoutJitter = backoffMax; | |
| } | |
| else | |
| { | |
| delayWithoutJitter = backoffInitial * Math.Pow(backoffBase, effectiveExponent); | |
| } | |
| return (int)(j * delayWithoutJitter); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| var subject = CreateSubject(coreSession: mockCoreSession.Object, clock: mockClock.Object); | ||
| SetupTransactionState(subject, true); |
There was a problem hiding this comment.
Is this related to the ticket?
There was a problem hiding this comment.
Yes, changes were rolled back. Thank you for noticing this.
| var backoffTime = await ExecuteWithTransactionAsync(1); | ||
|
|
||
| var difference = backoffTime - noBackoffTime - TimeSpan.FromSeconds(2.2); | ||
| difference.Should().BeLessThan(TimeSpan.FromMilliseconds(1)); |
There was a problem hiding this comment.
Are we using a 1 millisecond window here instead of a 1 second because we're "faking" the prose test?
There was a problem hiding this comment.
Yep. We do not interact with the server, so timing should be more strict. 1ms could be small though, we might will need to adjust it in future.
| var noBackoffTime = await ExecuteWithTransactionAsync(0); | ||
| var backoffTime = await ExecuteWithTransactionAsync(1); | ||
|
|
||
| var difference = backoffTime - noBackoffTime - TimeSpan.FromSeconds(2.2); |
There was a problem hiding this comment.
Will
backoffTimeMS.Should().BeApproximately(noBackoffTimeMS + 2200, 1);
be more readable?
| { | ||
| return callbackOutcome.Result; // assume callback intentionally ended the transaction | ||
| } | ||
| if (IsTransactionInStartingOrInProgressState(clientSession)) |
There was a problem hiding this comment.
Similar to ShouldRetryTransaction consider ShouldAbortTransaction(out AbortTransactionOptions)
| { | ||
| return callbackOutcome.Result; // assume callback intentionally ended the transaction | ||
| } | ||
| if(ShouldAbortTransaction(operationContext, clientSession, out var abortOptions)) |
There was a problem hiding this comment.
minor: space after if in both places.
13422a7 to
02ea474
Compare
No description provided.